package wsi.function;

import java.io.File;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.rygh.core.face.IEquable;
import org.rygh.core.face.IHasName;
import org.rygh.core.util.UIo;
import org.rygh.core.xml.XmlLeaf;
import org.rygh.core.xml.XmlObject;
import org.rygh.core.xml.XmlTree;
import org.rygh.semantic.model.rdf.Entity;
import org.rygh.semantic.model.rdf.Fact;
import org.rygh.semantic.model.rdf.Predicate;
import org.scribe.model.Token;

import wsi.script.AppConfig;

/**
 * 
 * @author nanmus
 * 
 *         for parameters:
 * 
 *         $0:format
 * 
 *         1. specify the type y:wordnet_singer_110599806
 * 
 *         2. specify screening fact A {?x y:hasWonPrize
 *         y:Nobel_Prize_in_Literature }
 * 
 *         3. specify screening fact type B: {e1 t:-at-id ?x} where e1 comes
 *         from candidate_metadata.txt
 * 
 */
public class Function implements IHasName, IEquable {
	public enum Phrase {
		/**/REST,
		/**/INPUT,
		/**/DATA_GUIDE,
		/**/YAGO_HOP,
		/**/TYPE,
		/**/LOCATE_INPUT,
		/**/MAPPING,
		/**/FUNC_XPATH,
		/**/SUBTREE_MAPPING,
		/**/RESOLVE,
		/**/TRIPLES,
		/**/FUNC_DEF,
		/**/MEASURE,
	}

	public enum PhraseEx {
		SAMPLING, SELECTED_TREES,
	}

	enum Tag {
		name, notes,
	}

	public final static String[][] SKIP_RESET_ARRAY = {
	/**/{ "SKIP.INPUT", "true" },
	/**/{ "SKIP.DATA_GUIDE", "true" },
	/**/{ "SKIP.TYPE", "true" },
	/**/{ "SKIP.REST", "false" },
	/**/{ "SKIP.LOCATE_INPUT", "true" },
	/**/{ "SKIP.YAGO_HOP", "true" },
	/**/{ "SKIP.MAPPING", "true" },
	/**/{ "SKIP.FUNC_XPATH", "true" },
	/**/{ "SKIP.SUBTREE_MAPPING", "true" },
	/**/{ "SKIP.RESOLVE", "true" },
	/**/{ "SKIP.TRIPLES", "true" },
	/**/{ "SKIP.FUNC_DEF", "true" },
	/**/{ "SKIP.MEASURE", "true" }, };

	private final Set<Phrase> skippedPhrases = new HashSet<>();

	private final String service;
	private final String name;

	private String notes;
	private String oauth;
	private String format;
	private String method;
	private String params;
	private String convert;
	private String idPredicate;
	private String namePredicate;
	private Url url;

	private Token token;
	private Parameter parameter;
	private RuntimeInfo runtimeInfo;
	private Set<Predicate> idPredicates;

	private int hop = 1;

	private boolean direct = true;

	public Function(String service, String name) {
		this.service = service;
		this.name = name;
	}

	private void eat(String key, String val) {
		assert key.charAt(0) == '$' : String.format("%s: %s", name, key);

		parameter = new Parameter(key);

		// System.out.println("eat " + val);

		if (val.charAt(0) != '{') {
			Entity type = new Entity(val, false);
			assert type != null;

			parameter.setType(type);
		} else {
			String str = val.substring(1, val.length() - 1);
			String[] values = str.split(" ");

			Fact fact = new Fact(values[0], values[1], values[2]);
			parameter.setFilterFact(fact);
		}

	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (!(obj instanceof Function))
			return false;
		Function other = (Function) obj;
		if (name == null) {
			if (other.name != null)
				return false;
		} else if (!name.equals(other.name))
			return false;
		if (service == null) {
			if (other.service != null)
				return false;
		} else if (!service.equals(other.service))
			return false;
		return true;
	}

	@Override
	public boolean equalsTo(Object o) {
		return equals(o);
	}

	public String getConvert() {
		return convert;
	}

	public String getEscapedName() {
		String new_name;

		new_name = name.substring(3);
		new_name = new_name.replaceAll("Info", "");
		new_name = new_name.replaceAll("Track", "Song");
		new_name = new_name.replaceAll("Release", "Album");

		return new_name;
	}

	public String getFormat() {
		return format;
	}

	public String getFullName() {
		return String.format("%s_%s", service, name);
	}

	public int getHop() {
		return hop;
	}

	public String getIdPredicate() {
		return idPredicate;
	}

	public Set<Predicate> getIdPredicates() {
		return idPredicates;
	}

	public String getMethod() {
		return method;
	}

	@Override
	public String getName() {
		return name;
	}

	public String getNamePredicate() {
		return namePredicate;
	}

	public String getNote() {
		return notes;
	}

	public String getOauth() {
		return oauth;
	}

	public Parameter getParameter() {
		return parameter;
	}

	public String getParams() {
		return params;
	}

	public String[] getParams(int index, String val, String convert) {
		if (convert != null)
			val = val.replace("_", convert);

		String[] vals = params.split(",");

		for (int i = 0; i < vals.length; i++)
			vals[i] = vals[i].replace(String.format("[$%d]", index), val);

		return vals;
	}

	public RuntimeInfo getRuntimeInfo() {
		return runtimeInfo;
	}

	public String getService() {
		return service;
	}

	public Token getToken() {
		return token;
	}

	public Url getUrl() {
		return url;
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((name == null) ? 0 : name.hashCode());
		result = prime * result + ((service == null) ? 0 : service.hashCode());
		return result;
	}

	public boolean isDirect() {
		return direct;
	}

	public boolean isScheduled() {
		for (Phrase p : Phrase.values()) {
			if (p == Phrase.REST)
				continue;

			if (!skippedPhrases.contains(p))
				return true;
		}

		return false;
	}

	public boolean isSkipped(Phrase p) {
		return skippedPhrases.contains(p);
	}

	public void reset() {
		for (String[] s : SKIP_RESET_ARRAY)
			update(s[0], s[1]);
	}

	public void save() {
		File dir = new File(AppConfig.shared().getFunctionsFolder(), service);

		UIo.saveFile(new File(dir, String.format("%s.txt", getName())),
				toMachineString());

		// UIo.saveFile(new File(dir, String.format("%s.xml", getName())),
		// toXmlString());
	}

	public void setIdPredicate(String idPredicate) {
		this.idPredicate = idPredicate;
	}

	public void setIdPredicates(Set<Predicate> idPredicates) {
		this.idPredicates = idPredicates;
	}

	private void setNamePredicate(String namePredicate) {
		this.namePredicate = namePredicate;
	}

	public void setOauth(String oauth) {
		this.oauth = oauth;
	}

	public void setRuntimeInfo(RuntimeInfo runtimeInfo) {
		this.runtimeInfo = runtimeInfo;

		if (isDirect()) {
			Predicate name_pred = runtimeInfo.getNamePredicate();
			assert (name_pred != null) : runtimeInfo.getQueryId() + ", "
					+ getFullName();

			setNamePredicate(name_pred.getValue());
		}
	}

	public void setToken(Token token) {
		this.token = token;
	}

	/**
	 * setup for clean run w.r.t. script.
	 * 
	 * @param runtimeInfo
	 */
	public void setup(RuntimeInfo runtimeInfo) {
		setRuntimeInfo(runtimeInfo);

		skippedPhrases.clear();

		List<Phrase> phrases = AppConfig.shared().getSkippedPhrases();

		for (Phrase p : phrases)
			skippedPhrases.add(p);
	}

	public String toMachineString() {
		StringBuilder sbuilder = new StringBuilder();

		sbuilder.append(String.format("NAME:%s\r\n", name));
		sbuilder.append(String.format("NOTE:%s\r\n", notes));
		sbuilder.append(String.format("DIRECT:%b\r\n", direct));
		sbuilder.append(String.format("YAGO.HOP:%d\r\n", hop));
		sbuilder.append(String.format("URL:%s\r\n", url));
		assert url != null;

		if (idPredicate != null)
			sbuilder.append(String.format("ID_PRED:%s\r\n", idPredicate));
		if (namePredicate != null)
			sbuilder.append(String.format("NAME_PRED:%s\r\n", namePredicate));
		if (method != null)
			sbuilder.append(String.format("METHOD:%s\r\n", method));
		if (params != null)
			sbuilder.append(String.format("PARAMS:%s\r\n", params));
		if (convert != null)
			sbuilder.append(String.format("CONVERT:%s\r\n", convert));
		if (oauth != null) {
			sbuilder.append(String.format("OAUTH:%s\r\n", oauth));

			if (token != null)
				sbuilder.append(String.format("OAUTH_TOKEN:%s,%s\r\n",
						token.getToken(), token.getSecret()));
		}

		if (format != null && !format.equals("xml"))
			sbuilder.append(String.format("FORMAT:%s\r\n", format));

		sbuilder.append(String.format("SKIP.INPUT:%b\r\n",
				isSkipped(Phrase.INPUT)));
		sbuilder.append(String.format("SKIP.DATA_GUIDE:%b\r\n",
				isSkipped(Phrase.DATA_GUIDE)));
		sbuilder.append(String.format("SKIP.TYPE:%b\r\n",
				isSkipped(Phrase.TYPE)));
		sbuilder.append(String.format("SKIP.REST:%b\r\n",
				isSkipped(Phrase.REST)));
		sbuilder.append(String.format("SKIP.LOCATE_INPUT:%b\r\n",
				isSkipped(Phrase.LOCATE_INPUT)));
		sbuilder.append(String.format("SKIP.YAGO_HOP:%b\r\n",
				isSkipped(Phrase.YAGO_HOP)));
		sbuilder.append(String.format("SKIP.MAPPING:%b\r\n",
				isSkipped(Phrase.MAPPING)));
		sbuilder.append(String.format("SKIP.FUNC_XPATH:%b\r\n",
				isSkipped(Phrase.FUNC_XPATH)));
		sbuilder.append(String.format("SKIP.SUBTREE_MAPPING:%b\r\n",
				isSkipped(Phrase.SUBTREE_MAPPING)));
		sbuilder.append(String.format("SKIP.RESOLVE:%b\r\n",
				isSkipped(Phrase.RESOLVE)));
		sbuilder.append(String.format("SKIP.TRIPLES:%b\r\n",
				isSkipped(Phrase.TRIPLES)));
		sbuilder.append(String.format("SKIP.FUNC_DEF:%b\r\n",
				isSkipped(Phrase.FUNC_DEF)));
		sbuilder.append(String.format("SKIP.MEASURE:%b\r\n",
				isSkipped(Phrase.MEASURE)));

		sbuilder.append(parameter);
		sbuilder.append("\r\n");

		return sbuilder.toString();
	}

	@Override
	public String toString() {
		return getFullName();
	}

	public String toXmlString() {
		XmlTree tree = new XmlTree();

		XmlObject object = new XmlObject("function");
		object.addAttribute("name", name);
		object.addAttribute("direct", Boolean.toString(direct));
		XmlLeaf root = new XmlLeaf(tree.getRoot(), object);

		new XmlLeaf(root, new XmlObject("notes", notes));
		new XmlLeaf(root, new XmlObject("hop", Integer.toString(hop)));
		new XmlLeaf(root, new XmlObject("url", url.toString()));

		if (idPredicate != null)
			new XmlLeaf(root, new XmlObject("id_pred", idPredicate));
		if (namePredicate != null)
			new XmlLeaf(root, new XmlObject("name_pred", namePredicate));
		if (method != null)
			new XmlLeaf(root, new XmlObject("method", method));
		if (params != null)
			new XmlLeaf(root, new XmlObject("params", params));
		if (convert != null)
			new XmlLeaf(root, new XmlObject("convert", convert));
		if (oauth != null) {
			object = new XmlObject("oauth", oauth);

			if (token != null) {
				object.addAttribute("token", token.getToken());
				object.addAttribute("secrect", token.getSecret());
			}

			new XmlLeaf(root, object);
		}

		if (format != null && !format.equals("xml"))
			new XmlLeaf(root, new XmlObject("format", format));

		XmlLeaf skip = new XmlLeaf(root, new XmlObject("skip"));

		new XmlLeaf(skip, new XmlObject("input",
				Boolean.toString(isSkipped(Phrase.INPUT))));
		new XmlLeaf(skip, new XmlObject("dataguide",
				Boolean.toString(isSkipped(Phrase.DATA_GUIDE))));
		new XmlLeaf(skip, new XmlObject("type",
				Boolean.toString(isSkipped(Phrase.TYPE))));
		new XmlLeaf(skip, new XmlObject("rest",
				Boolean.toString(isSkipped(Phrase.REST))));
		new XmlLeaf(skip, new XmlObject("locate",
				Boolean.toString(isSkipped(Phrase.LOCATE_INPUT))));
		new XmlLeaf(skip, new XmlObject("extract",
				Boolean.toString(isSkipped(Phrase.YAGO_HOP))));
		new XmlLeaf(skip, new XmlObject("mapping",
				Boolean.toString(isSkipped(Phrase.MAPPING))));
		new XmlLeaf(skip, new XmlObject("func_xpath",
				Boolean.toString(isSkipped(Phrase.FUNC_XPATH))));
		new XmlLeaf(skip, new XmlObject("subtree_mapping",
				Boolean.toString(isSkipped(Phrase.SUBTREE_MAPPING))));
		new XmlLeaf(skip, new XmlObject("resolve",
				Boolean.toString(isSkipped(Phrase.RESOLVE))));
		new XmlLeaf(skip, new XmlObject("triples",
				Boolean.toString(isSkipped(Phrase.TRIPLES))));
		new XmlLeaf(skip, new XmlObject("define",
				Boolean.toString(isSkipped(Phrase.FUNC_DEF))));
		new XmlLeaf(skip, new XmlObject("measure",
				Boolean.toString(isSkipped(Phrase.MEASURE))));

		return tree.toXmlString();
	}

	public void update(String key, String val) {
		assert val != null : key;

		if (val.equals("&nbsp;"))
			val = " ";

		switch (key) {
		case "NAME":
			assert name.equals(val) : name + ", " + val;
			break;

		case "NOTE":
			notes = val;
			break;

		case "ID_PRED":
			idPredicate = val;
			break;

		case "NAME_PRED":
			namePredicate = val;
			break;

		case "METHOD":
			method = val;
			break;

		case "PARAMS":
			params = val;
			break;

		case "DIRECT":
			direct = Boolean.parseBoolean(val);
			break;

		case "YAGO.HOP":
			hop = Integer.parseInt(val);
			break;

		case "OAUTH":
			oauth = val;
			break;

		case "OAUTH_TOKEN":
			String[] vals = val.split(",");
			token = new Token(vals[0], vals[1]);
			break;

		case "URL":
			url = new Url(val);
			break;

		case "SKIP.REST":
			if (Boolean.parseBoolean(val))
				skippedPhrases.add(Phrase.REST);
			else
				skippedPhrases.remove(Phrase.REST);
			break;

		case "SKIP.INPUT":
			if (Boolean.parseBoolean(val))
				skippedPhrases.add(Phrase.INPUT);
			else
				skippedPhrases.remove(Phrase.INPUT);
			break;

		case "SKIP.DATA_GUIDE":
			if (Boolean.parseBoolean(val))
				skippedPhrases.add(Phrase.DATA_GUIDE);
			else
				skippedPhrases.remove(Phrase.DATA_GUIDE);
			break;

		case "SKIP.TYPE":
			if (Boolean.parseBoolean(val))
				skippedPhrases.add(Phrase.TYPE);
			else
				skippedPhrases.remove(Phrase.TYPE);
			break;

		case "SKIP.LOCATE_INPUT":
			if (Boolean.parseBoolean(val))
				skippedPhrases.add(Phrase.LOCATE_INPUT);
			else
				skippedPhrases.remove(Phrase.LOCATE_INPUT);
			break;

		case "SKIP.YAGO_HOP":
			if (Boolean.parseBoolean(val))
				skippedPhrases.add(Phrase.YAGO_HOP);
			else
				skippedPhrases.remove(Phrase.YAGO_HOP);
			break;

		case "SKIP.MAPPING":
			if (Boolean.parseBoolean(val))
				skippedPhrases.add(Phrase.MAPPING);
			else
				skippedPhrases.remove(Phrase.MAPPING);
			break;

		case "SKIP.FUNC_XPATH":
			if (Boolean.parseBoolean(val))
				skippedPhrases.add(Phrase.FUNC_XPATH);
			else
				skippedPhrases.remove(Phrase.FUNC_XPATH);
			break;

		case "SKIP.SUBTREE_MAPPING":
			if (Boolean.parseBoolean(val))
				skippedPhrases.add(Phrase.SUBTREE_MAPPING);
			else
				skippedPhrases.remove(Phrase.SUBTREE_MAPPING);
			break;

		case "SKIP.RESOLVE":
			if (Boolean.parseBoolean(val))
				skippedPhrases.add(Phrase.RESOLVE);
			else
				skippedPhrases.remove(Phrase.RESOLVE);
			break;

		case "SKIP.TRIPLES":
			if (Boolean.parseBoolean(val))
				skippedPhrases.add(Phrase.TRIPLES);
			else
				skippedPhrases.remove(Phrase.TRIPLES);
			break;

		case "SKIP.FUNC_DEF":
			if (Boolean.parseBoolean(val))
				skippedPhrases.add(Phrase.FUNC_DEF);
			else
				skippedPhrases.remove(Phrase.FUNC_DEF);
			break;

		case "SKIP.MEASURE":
			if (Boolean.parseBoolean(val))
				skippedPhrases.add(Phrase.MEASURE);
			else
				skippedPhrases.remove(Phrase.MEASURE);
			break;

		case "CONVERT":
			convert = val;
			break;

		case "FORMAT":
			format = val;
			break;

		default:
			eat(key, val);
			break;
		}
	}

}
